#pragma once

#include <core/noncopyable.h>
#include <core/smart_ptr.h>

#include "Particle.h"
#include "Zone.h"




namespace math
{
   /**
    * @brief   An abstract class that defines a physical object acting on particles.
    *
    * A Modifier is first defined by a Zone. If the particle enters the Zone of the Modifier,
    * its action is applied to the Particle.
    *
    * If no Zone is attached to a Modifier, the Zone is considered to be the entire scene.
    */
   template
   <
      typename float_t
   >
   class ParticleModifier : public core::noncopyable
   {
   public:

      enum Trigger
      {
         TRIGGER_WITHIN_ZONE,
         TRIGGER_OUTSIDE_ZONE,
         TRIGGER_ALWAYS
      };

   private:

      ZonePtr         m_zone;
      const Trigger   m_trigger;

   public:

      /**
       * Create a ParticleModifier using the specified Zone and trigger.
       * By default, the ParticleModifier operates on the entire scene and triggers
       * when particles are within it's zone.
       *
       * @param zone      the zone of this ParticleModifier
       * @param trigger   the trigger that causes particles to be modified
       */
      ParticleModifier(const ZonePtr& zone = ZonePtr(), Trigger trigger = TRIGGER_WITHIN_ZONE)
         : m_zone(zone)
         , m_trigger(trigger)
      {}

      virtual ~ParticleModifier() {}



      /**
       * Get the zone of the Modifier.
       *
       * @warning   may be NULL
       */
      ZonePtr zone() const
      {
         return m_zone;
      }



      /**
       * Handles modification of the given particle.
       * Tests if the particle shall be modified and then subsequently modifies it.
       * Otherwise does nothing.
       */
      void handleParticle(Particle& particle, float_t deltaTime) const
      {
         // There must either be no zone (infinite boundaries) or the particle
         // must meet a certain spatial criterum, such as inside or outside the Zone
         if(!m_zone || triggers(particle))
         {
            modify(particle, deltaTime);
         }
      }

   private:

      /**
       * Tests if the given particle triggers an action or not.
       */
      bool triggers(const Particle& particle) const
      {
         switch(m_trigger)
         {
         case TRIGGER_ALWAYS:       return true;
         case TRIGGER_WITHIN_ZONE:  return m_zone->isWithin(particle);
         case TRIGGER_OUTSIDE_ZONE: return m_zone->isOutside(particle);
         default: /*TODO: Throw exception*/ return false;
         }
      }

      /**
       * Modify the given particle.
       */
      virtual void modify(Particle& particle, float_t deltaTime) const = 0;
   };
   ///////////////////////////////////////////////////////////////////////////////////////////////

   typedef core::smart_ptr<ParticleModifier,core::ref_counted,core::throw_check,core::heap_storage> ParticleModifierPtr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
